/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grex.WekaModels;

import grex.Data.ArffTableModel;
import grex.Environment;
import grex.ErrorManagement.ErrorManager;
import grex.IPredictiveModel;
import grex.Data.Prediction;
import grex.Data.PredictionContainer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import weka.attributeSelection.AttributeSelection;
import weka.attributeSelection.CfsSubsetEval;
import weka.attributeSelection.GreedyStepwise;
import weka.classifiers.Classifier;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import wekaGrexBridge.WekaArffTableModel;

/**
 *
 * @author RIK
 */
public abstract class WekaPredictiveModel implements IPredictiveModel {
    protected Classifier model;
    ArffTableModel orgData;
    protected WekaArffTableModel wekaArffTableModel;
    protected PredictionContainer pcTrain, pcTest, pcVal;
    protected Instances wekaTrain, wekaTest, wekaVal, tmp;
    String currentFold; 
    protected double[][] orgTest,orgTrain,orgVal;
    protected boolean trained =false;
    
    WekaPredictiveModel(){};
    
    public WekaPredictiveModel(ArffTableModel data, Classifier model) {      
            this.model = model;
           // data.initAllFolds();
            orgData = data;
            wekaArffTableModel = new WekaArffTableModel(data);        
    }
    

    

    
    public WekaPredictiveModel(Environment env, Classifier model) {//this constructor is only used when the mdoel is used as an node in a GP. 
        this.model = model;
        wekaArffTableModel = new WekaArffTableModel(env);
    }    
    
    public WekaPredictiveModel(Classifier model) {
        this.model = model;
    }
    
    public String getFold(){
        return currentFold;
    }
    
    public void initFold(String fold) {
       // wekaArffTableModel.initFold(fold);
        currentFold = fold;

        orgTrain = wekaArffTableModel.getTrainFold(fold);
        //wekaTrain = wekaArffTableModel.getTrainInstances(fold);
         wekaTrain = wekaArffTableModel.getEmptyInstances();
        pcTrain = new PredictionContainer(wekaTrain.size());
        
        
        
        orgTest = wekaArffTableModel.getTestFold(fold);
        //wekaTest = wekaArffTableModel.getTestInstances(fold);   
        wekaTest = wekaArffTableModel.getEmptyInstances(); 
        pcTest = new PredictionContainer(wekaTest.size());
        
        orgVal = wekaArffTableModel.getValFold(fold);
        //wekaVal = wekaArffTableModel.getValInstances(fold);
        wekaVal = wekaArffTableModel.getEmptyInstances();
        pcVal = new PredictionContainer(wekaVal.size());
        
        boolean classifcation = wekaArffTableModel.isClassificationTask();
        int nrOfTargetValues = wekaArffTableModel.getNrOfTargetCategories();
        for(int i=0;i<orgTrain.length;i++){
            double[] row = orgTrain[i];
            pcTrain.put(row, new Prediction(row,classifcation,nrOfTargetValues));
            DenseInstance inst = new DenseInstance(1, row);
            inst.setDataset(wekaTrain);
            wekaTrain.add(inst);        
        }
        
        for(int i=0;i<orgVal.length;i++){
            double[] row = orgVal[i];
            pcVal.put(row, new Prediction(row,classifcation,nrOfTargetValues));
            DenseInstance inst = new DenseInstance(1, row);
            inst.setDataset(wekaVal);
            wekaVal.add(inst);        
        }
        
        for(int i=0;i<orgTest.length;i++){
            double[] row = orgTest[i];
            pcTest.put(row, new Prediction(row,classifcation,nrOfTargetValues));
            DenseInstance inst = new DenseInstance(1, row);
            inst.setDataset(wekaTest);
            wekaTest.add(inst);        
        }
        
        
        /*
        for (int i = 0; i < wekaTrain.size(); i++){//Instance i : wekaTrain) {
            double[] instance = wekaTrain.instance(i).toDoubleArray();
            orgTrain[i] = instance; //Reset values to faviltiate an ordered presentation of the prediction. OrgTest and orgTrain is only used to sort the predictions since they are stored in a hastable
            pcTrain.put(instance, new Prediction(instance));
        }
        for (int i = 0; i < wekaTest.size();i++) {
            double[] instance = wekaTest.instance(i).toDoubleArray();
            orgTest[i] = instance; //Reset values to faviltiate an ordered presentation of the prediction. OrgTest and orgTrain is only used to sort the predictions since they are stored in a hastable
            pcTest.put(instance, new Prediction(instance));
        }
        for (int i = 0; i < wekaVal.size();i++) {
            double[] instance = wekaVal.instance(i).toDoubleArray();
            orgVal[i] = instance; //Reset values to faviltiate an ordered presentation of the prediction. OrgTest and orgTrain is only used to sort the predictions since they are stored in a hastable
            pcVal.put(instance, new Prediction(instance));
        }*/
        trained = false;
    }
    
    public String getOrderedTestPredictions(){
        String s ="Target\tPrediction\n";
        execute(pcTest);
        for (int i = 0; i < orgTest.length; i++) {
            double[] ds = orgTest[i];
            Prediction p = pcTest.get(ds);
            s+=p.getTargetValue() +"\t" + p.getPrediction()+"\n";            
        }
        
        return s;
        
    }

    public PredictionContainer getPcTest() {
        return pcTest;
    }
    
    public PredictionContainer getPcTrain() {
        return pcTrain;
    }
    
    public PredictionContainer getPcVal(){
        return pcVal;
    }
    
    public double[] getProbsForPrediction(Prediction p){
        try{
         Instance instance = wekaArffTableModel.getInstance(p.getInstance(),wekaTrain);//wekaTrain is just used to set the Dataset in the instance
         return model.distributionForInstance(instance);
         } catch (Exception ex) {
                ErrorManager.getInstance().reportError(ex);
        }
        return null;
    }
    
    public double getPredictionForPrediction(Prediction p){
        try{
         Instance instance = wekaArffTableModel.getInstance(p.getInstance(),wekaTrain);//wekaTrain is just used to set the Dataset in the instance
         return model.classifyInstance(instance);
         } catch (Exception ex) {
                ErrorManager.getInstance().reportError(ex);
        }
        return -999999;
    }
    
    
    public void execute(PredictionContainer pc) {
        for (Prediction p : pc.values()) {

            try {
                Instance instance = wekaArffTableModel.getInstance(p.getInstance(),wekaTrain);//wekaTrain is just used to set the Dataset in the instance
                double prediction;
                
                prediction = model.classifyInstance(instance);
                
                p.setProbs(model.distributionForInstance(instance));
                p.setPrediction(prediction);
             
            } catch (Exception ex) {
                ErrorManager.getInstance().reportError(ex);
            }

        }
    }
    
    public double[] calcSensitivity(){
        
        double[] sens=new double[wekaTrain.numAttributes()-1];
        if(model==null) return sens; //Ensembles does not have its own model.
           for (Prediction p : pcTrain.values()) {
            try {                
                double[] dIns = p.getInstance();
                Instance ins = wekaArffTableModel.getInstance(dIns,wekaTrain);//wekaTrain is just used to set the Dataset in the instance
                double pNorm = model.classifyInstance(ins);
                for(int i=0; i < dIns.length-1; i++){
                    dIns = p.getInstance();
                    double tmpValue = dIns[i];
                    dIns[i] *= 1.1;
                    ins = wekaArffTableModel.getInstance(dIns,wekaTrain);
                    double pMod = model.classifyInstance(ins);
                    sens[i] += Math.abs(pMod-pNorm);
                    dIns[i] = tmpValue;
                }                             
            } catch (Exception ex) {
                ErrorManager.getInstance().reportError(ex);
            }            
        }
           //Normalize results
           
           double tot = 0;
           for(int i = 0; i<sens.length;i++)
               tot += sens[i];
           if(tot==0)
               return sens;
           for(int i = 0; i<sens.length;i++)
               sens[i] = sens[i]/tot;        
        return sens;
    }
    public boolean isTrained(){
        return trained;
    }
    
    public void train() {
        try {
            model.buildClassifier(wekaTrain);
            execute(pcTrain);//set values in prediction container
            trained = true;
        } catch (Exception ex) {
            ErrorManager.getInstance().reportError(ex);
        }
    }
    
    public void train(PredictionContainer pcFromAParenGeneInAGP) { //only used when internal node in a GP
        try {
            wekaTrain = wekaArffTableModel.getInstances(pcFromAParenGeneInAGP);
            model.buildClassifier(wekaTrain);
        } catch (Exception ex) {
            ErrorManager.getInstance().reportError(ex);
        }
    }
   
    

    
    protected int calcNumberOfNodes(String graph) {
        try {
            StringTokenizer line = new StringTokenizer(graph, "\n");
            line.nextElement(); //Remove header
            int numberOfNodes = 0;
            int numberOfLines = line.countTokens();
            for (int i = 0; i < numberOfLines - 1; i++) {//Remove footer
                StringTokenizer st = new StringTokenizer(line.nextToken(), " -N");
                int nodeNr = Integer.parseInt(st.nextToken());
                if (numberOfNodes < nodeNr) {
                    numberOfNodes = nodeNr;
                }
            }

            return numberOfNodes + 1; //node count starts att zero
        } catch (Exception e) {
            e.printStackTrace();
            return -999;
        }
    }
}
